// Ninf-G-XX, ver. 0.1
// Copyright 2011 National Institute of Advanced Industrial Science and Technology
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <fstream>

#include "HttpProtocol.hh"

#include <iostream>

#include <stdio.h>
#include <curl/curl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#include <string.h>
#include "URI.hpp"

using namespace std;
using namespace ninf::lib;

static off_t getFileSize(const char * filename){
  struct stat buf;
  if (stat(filename, &buf) != 0){
    perror("stat");
    fprintf(stderr, "%s\n", filename);
    return -1;
  }
  fprintf(stderr, "%s: %d\n", filename, buf.st_size);
  return buf.st_size;
}

class buffer {
public:
  buffer(const char * ptr, int size): ptr(ptr), size(size), offset(0){}
  const char * ptr;
  int    size;
  int    offset;
};

size_t HttpProtocol::memoryRecord(void* ptr, size_t size, size_t nb, void* object) {
  HttpProtocol* p = (HttpProtocol*) object;
  memcpy((void*) ((size_t) p->destPtr+p->pos), ptr, size*nb);
  p->pos+=size*nb;
  return size*nb;
}

size_t HttpProtocol::streamRead(void *ptr, size_t size, size_t nmemb, void *stream)
{
  size_t retcode;
  retcode = fread(ptr, size, nmemb, (FILE*)stream);
  fprintf(stderr, "*** We read %d bytes from file\n", retcode);
  return retcode;
}

size_t HttpProtocol::memoryRead(void *ptr, size_t size, size_t nmemb, void * _buf)
{
  size_t retcode;
  buffer * buf = (buffer *) _buf;

  int tobeCopied = size * nmemb > (buf->size - buf->offset)? buf->size - buf->offset : size * nmemb;
  if (tobeCopied == 0)
    return 0;
  memcpy(ptr, buf->ptr + buf->offset, tobeCopied);
  buf->offset += tobeCopied;

  fprintf(stderr, "*** We read %d bytes from mem\n", tobeCopied);
  return tobeCopied;
}


size_t HttpProtocol::nullOut(void* ptr, size_t size, size_t nb, void* stream) {
  return size*nb;
}

HttpProtocol::HttpProtocol() {
  addName("http");
  addName("https");
  addName("file");
}

HttpProtocol::HttpProtocol(const HttpProtocol& protocol) : Protocol(protocol) {

}

void HttpProtocol::downloadDataFile(const std::string& sourceURI,
                               const std::string& path,
                               const Credential& credential=Credential()) {
  CURL* dwHandle = curl_easy_init();

  if (dwHandle==NULL) throw ProtocolError("failed to open file");
  FILE* file = fopen(path.c_str(), "w");

  if (file==NULL) {
    curl_easy_cleanup(dwHandle);
    throw ProtocolError("failed to open file");
  }
  curl_easy_setopt(dwHandle, CURLOPT_WRITEDATA, file);
  curl_easy_setopt(dwHandle, CURLOPT_URL, sourceURI.c_str());
  curl_easy_perform(dwHandle);
  curl_easy_cleanup(dwHandle);
  fclose(file);
}

void HttpProtocol::downloadDataFile(const std::string& sourceURI,
                                    const Credential& credential=Credential()) {
  std::string path = sourceURI.substr(sourceURI.find_last_of('/'));
  if (path[0]=='/')
    path=path.substr(1);
  downloadDataFile(sourceURI, path, credential);
}

void HttpProtocol::downloadDataFile(const std::list<std::string>& sourceURIs,
                      const std::list<Credential>& credentials=std::list<Credential>()) {
  /* Simple implementation: uses only one source
     without any credential.
  */
  downloadDataFile(sourceURIs.front());
}

void HttpProtocol::downloadDataFile(const std::list<std::string>& sourceURIs,
                                    const std::string& path,
                                    const std::list<Credential>& credentials=std::list<Credential>()) {
  /* Simple implementation: uses only one source
  without any credential.
  */
  downloadDataFile(sourceURIs.front(), path);
}

void HttpProtocol::downloadDataMemory(const std::string& sourceURI,
                                void* const dest,
                                const Credential& credential = Credential()) {
  CURL* dwHandle = curl_easy_init();

  if (dwHandle==NULL) throw ProtocolError("cannot init curl handle");

  destPtr=dest;
  pos=0;

  curl_easy_setopt(dwHandle, CURLOPT_WRITEFUNCTION, &memoryRecord);
  curl_easy_setopt(dwHandle, CURLOPT_WRITEDATA, this);
  curl_easy_setopt(dwHandle, CURLOPT_URL, sourceURI.c_str());
  curl_easy_perform(dwHandle);
  curl_easy_cleanup(dwHandle);
}

void HttpProtocol::downloadDataMemory(const std::string& sourceURI,
                                 void** dest,
                                 size_t* size,
                                 const Credential& credential=Credential()) {

  URI uri(sourceURI);
  if (uri.scheme != "file") {
    CURL* dwHandle = curl_easy_init();
    double dSize;

    curl_easy_setopt(dwHandle, CURLOPT_URL, sourceURI.c_str());
    curl_easy_setopt(dwHandle, CURLOPT_HEADER, 1);
    curl_easy_setopt(dwHandle, CURLOPT_NOBODY, 1);
    curl_easy_setopt(dwHandle, CURLOPT_WRITEFUNCTION, &nullOut);

    curl_easy_perform(dwHandle);
    curl_easy_getinfo(dwHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize);
    curl_easy_cleanup(dwHandle);

    *size=dSize;
  } else {
    // this is a patch to cope with libcurl bug on getting size for file scheme
    // the bug will be fixed in the next release, making this branch useless.
    *size = getFileSize((string("/") + uri.path).c_str());
    if (*size < 0)
        throw ProtocolError("failed to get data size");
  }

  *dest=calloc(*size,1);
  downloadDataMemory(sourceURI, *dest, credential);
}

void HttpProtocol::downloadDataMemory(const std::list<std::string>& sourceURIs,
                                      void* const dest,
                                      const std::list<Credential>& credential) {
  downloadDataMemory(sourceURIs.front(), dest);
}

void HttpProtocol::downloadDataMemory(const std::list<std::string>& sourceURIs,
                                      void** dest,
                                      size_t* size,
                                      const std::list<Credential>& credentials) {
  downloadDataMemory(sourceURIs.front(), dest, size);
}

void HttpProtocol::uploadDataFile(const std::string& destinationURI,
                    const std::string& path,
                    const Credential& credential=Credential()) {

  struct stat file_info;

  int hd = open(path.c_str(), O_RDONLY) ;
  fstat(hd, &file_info);
  close(hd) ;

  FILE* hd_src = fopen(path.c_str(), "rb");
  if (hd_src == NULL) throw ProtocolError("failed to open file");

  CURL* upHandle = curl_easy_init();
  if (upHandle==NULL) throw ProtocolError("failed to get curl handle");

  curl_easy_setopt(upHandle, CURLOPT_READFUNCTION, &streamRead);

  /* enable uploading */
  curl_easy_setopt(upHandle, CURLOPT_UPLOAD, 1L);

  /* HTTP PUT please */
  curl_easy_setopt(upHandle, CURLOPT_PUT, 1L);

  /* specify target URL, and note that this URL should include a file
     name, not only a directory */
  curl_easy_setopt(upHandle, CURLOPT_URL, destinationURI.c_str());

  /* now specify which file to upload */
  curl_easy_setopt(upHandle, CURLOPT_READDATA, hd_src);

  /* provide the size of the upload, we specicially typecast the value
     to curl_off_t since we must be sure to use the correct data size */
  curl_easy_setopt(upHandle, CURLOPT_INFILESIZE_LARGE,
                   (curl_off_t)file_info.st_size);

  /* Now run off and do what you've been told! */

  if (curl_easy_perform(upHandle) != 0) {
    std::cout << "error\n";
    throw ProtocolError("failed to perform curl operation");
  }
  fclose(hd_src);
}

void HttpProtocol::uploadDataMemory(const std::string& destinationURI,
                                  void* const src,
                                  size_t      src_size,
                                  const Credential& credential=Credential()) {

  CURL* upHandle = curl_easy_init();

  if (upHandle==NULL) throw ProtocolError();
  int res;

  res = curl_easy_setopt(upHandle, CURLOPT_READFUNCTION, &memoryRead);

  /* enable uploading */
  res = curl_easy_setopt(upHandle, CURLOPT_UPLOAD, 1L);

  /* HTTP PUT please */
  res = curl_easy_setopt(upHandle, CURLOPT_PUT, 1L);

  /* specify target URL, and note that this URL should include a file
     name, not only a directory */
  res = curl_easy_setopt(upHandle, CURLOPT_URL, destinationURI.c_str());

  buffer buf((const char *)src, src_size);
  /* now specify which file to upload */
  res = curl_easy_setopt(upHandle, CURLOPT_READDATA, &buf);

  /* provide the size of the upload, we specicially typecast the value
     to curl_off_t since we must be sure to use the correct data size */
  res = curl_easy_setopt(upHandle, CURLOPT_INFILESIZE_LARGE,
                   (curl_off_t)src_size);

  /* Now run off and do what you've been told! */
  if (curl_easy_perform(upHandle) != 0) {
    throw ProtocolError("failed to perform curl operation");
  }
}

#ifdef MAIN
int main(int argc, char* argv[]) {
  HttpProtocol p;
//  char* destPtr = (char*) calloc(8350,1);
  char* destPtr;
  size_t size;

  p.downloadDataMemory("file:///tmp/saru",
      (void**) &destPtr, &size);
//  p.downloadDataMemory("http://curl.haxx.se/libcurl/c/curl_easy_perform.html",
//      (void**) &destPtr, &size);
  std::cout << "Resultat: " << std::endl << "******" << std::endl
    << destPtr << std::endl << "*********" << std::endl;
  std::cout << "Taille : " << size << std::endl;
}
#endif
